/*
 * xlogger.h
 *
 *  Created on: 2009-7-11
 *      Author: fanliangliang
 */

#ifndef XLOGGER_H_
#define XLOGGER_H_

// S60 sdk platform version detect.
#ifdef PLATFORM_VERSION
#undef PLATFORM_VERSION
#endif

#if !defined(EKA2) // S60 1st and 2nd Edition
	#include <cdbcols.h>
	// Use GPRS_IF_NAME macro to distinguish between S60v1 and S60v2.
	// Yeah, I know it sucks...
	#ifdef GPRS_IF_NAME
		#define PLATFORM_VERSION 10		// S60 1st Edition
	#else
		#define PLATFORM_VERSION 20		// S60 2nd Edition
	#endif
#else // S60 3rd Edition onwards
	#if defined(__S60_50__)
		#define PLATFORM_VERSION 50		// S60 5th Edition
	#elif defined(__SERIES60_3X__)
		#if defined(__SERIES60_30__)
			#define PLATFORM_VERSION 30 // S60 3rd Edition MR
		#elif defined(__SERIES60_31__)
			#define PLATFORM_VERSION 31 // S60 3rd Edition FP1
		#elif defined(__SERIES60_32__)
			#define PLATFORM_VERSION 32 // S60 3rd Edition FP2
		#endif
	#endif
#endif

#ifndef PLATFORM_VERSION
#define PLATFORM_VERSION 0 // Unknown platform version
#endif


// Checks if xlogger is enabled or not.
#ifndef ENABLE_XLOGGER
	// Not specified by client, xlogger is enabled by default in debug builds.
	#if defined(_DEBUG) || defined(__WINS__) || defined(WINS)
		#define HAS_XLOGGER
	#endif
#else
	// Specified by client.
	#if ENABLE_XLOGGER != 0
		#define HAS_XLOGGER
	#endif
#endif


// Checks if compiler support variadic macros __VAR_ARGS__
// and gcc extension macro __PRETTY_FUNCTION__.
#ifndef _MSC_VER // Definitely! VC6 not support
	#define SUPPORT_VARIADIC_MACRO
	#define SUPPORT_TRACE_FUNCTION
#endif


#include <f32file.h>


#ifdef HAS_XLOGGER

	#define __L(x) _L(x)
	#define __WFILE__ __L(__FILE__)
	#define CONCAT_IMPL(x, y) x##y
	#define CONCAT(x, y) CONCAT_IMPL(x, y)
	#define ANONYMOUS_VARIABLE(x) CONCAT(x, __LINE__)

	#ifdef SUPPORT_TRACE_FUNCTION
		#define XTRACE_FUNCTION TFunctionTracer ANONYMOUS_VARIABLE(_tracer_)(__PRETTY_FUNCTION__, __WFILE__, __LINE__)
	#else
		#define XTRACE_FUNCTION
	#endif

	#define XLOGGER(level) if (TLoggerWrapper logger = CLogger::InstanceFor(level)) ; else logger->SetLogParameters(level, __WFILE__, __LINE__)

	#define XASSERT_A(x) XASSERT_IMPL(x, B)
	#define XASSERT_B(x) XASSERT_IMPL(x, A)
	#define XASSERT_IMPL(x, next) XASSERT_A.AddParameter(#x, x).XASSERT_##next

	#define xassert(expr) if (expr) ; else XLOGGER(ELevelDebug).Asserter().LogExpression(#expr, TAsserter::TSentry(logger->Asserter())).XASSERT_A

	#define xdump  XLOGGER(ELevelTrace).HexDumper()
	#define xtrace XLOGGER(ELevelTrace)
	#define xdebug XLOGGER(ELevelDebug)
	#define xinfo  XLOGGER(ELevelInfo)
	#define xwarn  XLOGGER(ELevelWarn)
	#define xerror XLOGGER(ELevelError)
	#define xfatal XLOGGER(ELevelFatal)

#else

	#define XTRACE_FUNCTION

	struct TEmptyAsserter {
		TEmptyAsserter()
		:XASSERT_A(*this)
		,XASSERT_B(*this) {
			
		}
		
		void Panic(const TDesC&, TInt) {}
		void Leave(TInt) {}
		void Message(const TDesC&) {}
		
		TEmptyAsserter& XASSERT_A;
		TEmptyAsserter& XASSERT_B;
	};
	
	#define XASSERT_A(x) XASSERT_IMPL(x, B)
	#define XASSERT_B(x) XASSERT_IMPL(x, A)
	#define XASSERT_IMPL(x, next) XASSERT_A.XASSERT_##next
	
	#define xassert(expr) if (false) ; else TEmptyAsserter().XASSERT_A

	#ifdef SUPPORT_VARIADIC_MACRO
		#define xdump(args...)
		#define xtrace(args...)
		#define xdebug(args...)
		#define xinfo(args...)
		#define xwarn(args...)
		#define xerror(args...)
		#define xfatal(args...)
	#else
		#define xdump  _xempty
		#define xtrace _xempty
		#define xdebug _xempty
		#define xinfo  _xempty
		#define xwarn  _xempty
		#define xerror _xempty
		#define xfatal _xempty
	
		inline void _xempty(const TAny*, TInt) {}
		inline void _xempty(const TAny*, TInt, const char*, ...) {}
		inline void _xempty(const char*, ...) {}
	#endif

#endif


// Used to indicate a severity level for selecting which logging messages to report.
// The logging levels are in increasing order of severity.
enum TLogLevel {
	ELevelTrace, // Most detailed information, for verbose debugging.
	ELevelDebug, // Detailed information on the flow through the system.
	ELevelInfo,  // Interesting runtime events (startup/shutdown), should be conservative and keep to a minimum.
	ELevelWarn,  // Other runtime situations that are undesirable or unexpected, but not necessarily "wrong".
	ELevelError, // Other runtime errors or unexpected conditions.
	ELevelFatal, // Severe errors that cause premature termination.
	ELevelNone   // Special level used to disable all log messages.
};


enum {
	KMessageBufferSize = 2048
};

typedef TBuf<KMessageBufferSize> TLogBuffer;


// A class for log events.
// This class will be passed to MLogAppenders whenever a log event is fired
// and contains all the pertinent information to the event.
class TLogEvent {
public:
	TLogLevel iLevel;	// The level of this log event.
	TTime iTime;		// The timestamp of this log event.
	TLogBuffer iMessage;// The message to be logged.
	TFileName iFile;	// The source code file name of this log event.
	TInt iLine;			// The source code line number of this log event.
};


// A MLogAppender represents a device where the log events will be written to.
class MLogAppender {
public:
	// Virtual destructor.
	virtual ~MLogAppender() {}
	
	// Overwrite this method to do actual work when a log event is fired.
	virtual void DoAppendL(const TLogEvent& e) = 0;
};


// A CFileAppender write the log events to a file.
class CFileAppender : public MLogAppender {
public:
	static CFileAppender* NewL(const TDesC& aFilename, TUint aFileMode = EFileWrite | EFileStreamText);
	static CFileAppender* NewLC(const TDesC& aFilename, TUint aFileMode = EFileWrite | EFileStreamText);
	virtual ~CFileAppender();
	
	virtual void DoAppendL(const TLogEvent& e);
	
private:
	CFileAppender();
	void ConstructL(const TDesC& aFilename, TUint aFileMode);

private:
	RFs iFs;
	RFile iFile;
	HBufC* iBuffer;
};


class CLogger;
class TAsserter {
public:
	class TSentry {
	public:
		TSentry(TAsserter& aAsserter)
		:iAsserter(aAsserter) {
			
		}
		
		~TSentry() {
			iAsserter.Commit();
		}
	private:
		TAsserter& iAsserter;
	};

	friend class TSentry;
	
	template <typename T>
	friend TAsserter& AddParameterHelper(TAsserter& aAsserter, const char* aName, const TDesC& aFormat, const T& aValue);
	
public:
	TAsserter(CLogger& aLogger);
	~TAsserter();

	TAsserter& LogExpression(const char* aExpression, const TSentry& aSentry);
	TAsserter& AddParameter(const char* aName, const void* aValue);
	TAsserter& AddParameter(const char* aName, bool aValue);
	TAsserter& AddParameter(const char* aName, char aValue);
	TAsserter& AddParameter(const char* aName, const char* aValue);
	TAsserter& AddParameter(const char* aName, TInt32 aValue);
	TAsserter& AddParameter(const char* aName, TUint32 aValue);
	TAsserter& AddParameter(const char* aName, TReal32 aValue);
	TAsserter& AddParameter(const char* aName, TReal64 aValue);
	TAsserter& AddParameter(const char* aName, TChar aValue);
	TAsserter& AddParameter(const char* aName, const TDesC8& aValue);
	TAsserter& AddParameter(const char* aName, const TDesC16& aValue);
	TAsserter& AddParameter(const char* aName, const TDesC8* aValue);
	TAsserter& AddParameter(const char* aName, const TDesC16* aValue);
	TAsserter& AddParameter(const char* aName, const TPoint& aValue);
	TAsserter& AddParameter(const char* aName, const TSize& aValue);
	TAsserter& AddParameter(const char* aName, const TRect& aValue);
	TAsserter& AddParameter(const char* aName, const TTime& aValue);
	TAsserter& AddParameter(const char* aName, const TDateTime& aValue);
	
#if PLATFORM_VERSION > 20
	TAsserter& AddParameter(const char* aName, TInt64 aValue);
	TAsserter& AddParameter(const char* aName, TUint64 aValue);
#endif
	
	void Panic(const TDesC& aCategory, TInt aReason);
	void Leave(TInt aReason);
	void Message(const TDesC& aMessage);
	
private:
	TAsserter& AddDescriptorParameter(const char* aName, const TDesC16& aValue);
	TAsserter& AddOtherParameter(const char* aName, const TDesC16& aValue);
	TAsserter& AddParameterImplement(const char* aName, const TDesC16& aFormat, const TDesC& aValue);
	void Commit();
	void Commit(const TDesC& aMessage);
	
public:
	TAsserter& XASSERT_A;
	TAsserter& XASSERT_B;
	
private:
	CLogger& iLogger;
	TLogBuffer iBuffer;
};


class THexDumper {
public:
	THexDumper(CLogger& aLogger);
	~THexDumper();

	void Dump(const TDesC8& aData);
	void Dump(const TDesC16& aData);
	void Dump(const TAny* aData, TInt aSize);
	
	void Dump(const TDesC8& aData, const char* aFormat, ...);
	void Dump(const TDesC16& aData, const char* aFormat, ...);
	void Dump(const TAny* aData, TInt aSize, const char* aFormat, ...);
	
	void operator()(const TDesC8& aData);
	void operator()(const TDesC16& aData);
	void operator()(const TAny* aData, TInt aSize);
	
	void operator()(const TDesC8& aData, const char* aFormat, ...);
	void operator()(const TDesC16& aData, const char* aFormat, ...);
	void operator()(const TAny* aData, TInt aSize, const char* aFormat, ...);
	
private:
	void DumpHeader();
	void DumpSplitLine(TChar aChar);
	void DumpLine(TInt aLine, const TInt8* aData, TInt aSize);
	void DumpData(const TAny* aData, TInt aSize);
	void Dump(const TAny* aData, TInt aSize, const char* aFormat, VA_LIST aList);
	
private:
	CLogger& iLogger;
	TLogBuffer iBuffer;
};


class CCnvCharacterSetConverter;
class CLogger : public CBase {
	typedef void (*TLoggerInitializer)(CLogger*);
public:
	static CLogger* Instance();
	static CLogger* InstanceFor(TLogLevel aLevel);
	static bool IsInitialized();
	static void InitializeOnce(TLoggerInitializer aInitializer = NULL);
	static void DestroyOnce();
	static void DefaultInitializer(CLogger* aLogger);

	bool AddAppender(MLogAppender* aAppender);
	MLogAppender* RemoveAppender(MLogAppender* aAppender);

	bool IsEnabledFor(TLogLevel aLevel) const;
	void SetLevel(TLogLevel aLevel);
	TLogLevel Level() const;
	
	CLogger& SetLogParameters(TLogLevel aLevel, const TDesC& aFile, TInt aLine);
	void Log(const char* aFormat, VA_LIST aList);
	void Log(const char* aFormat, ...);
	void operator()(const char* aFormat, ...);
	
	HBufC16* ConvertToUnicode(const TDesC8& aForeign);
	TAsserter& Asserter();
	THexDumper& HexDumper();
	
private:
	static void Destroy();
	static CLogger* NewL();
	static CLogger* NewLC();
	CLogger();
	~CLogger();
	void ConstructL();

private:
	TLogLevel iLevel;
	TLogEvent iEvent;
	RPointerArray<MLogAppender> iAppenders;
	RFs iFs;
	CCnvCharacterSetConverter* iConverter;
	TInt iState;
	TInt iInitializeCount;
	TAsserter iAsserter;
	THexDumper iHexDumper;
};


// A CLogger wrapper class which use some tricks to avoid
// unnecessary function call overhead associate with CLogger::Instance().
class TLoggerWrapper {
public:
	TLoggerWrapper(CLogger* aLogger)
	:iLogger(aLogger) {

	}
	
	~TLoggerWrapper() {
		
	}
	
	operator bool() const {
		return iLogger == NULL;
	}
	
	CLogger* operator->() const {
		return iLogger;
	}
private:
	CLogger* iLogger;
};


// A simple function tracer that writes the function Enter, Exit and Leave to xlogger.
class TFunctionTracer {
public:
	// Use constructor to write log of entering a function.
	TFunctionTracer(const char* aFunction, const TDesC& aFile, TInt aLine);
	
	// Use destructor to write log of exiting a function normally or with a leave.
	~TFunctionTracer();
	
private:
	void Cleanup();
	static void OnCleanup(TAny* aObject);

private:
	CLogger* iLogger;
	HBufC* iFunction;
	HBufC* iFile;
	TInt iLine;
	bool iIsLeaved;
	bool iIsCleanedup;
};

#endif /* XLOGGER_H_ */
